//
//  ZMLogger.swift
//  hrhtv
//
//  Created by Ink on 2024-12-05.
//

public protocol LoggerProtocol {
    var storageKey: String? { get }
    func clearLogs()
}

extension ZMLogger:LoggerProtocol{}

// ZMLogger for managing log collection
public class ZMLogger<T:Codable> {
    public typealias PersistedType = [T]
    private var maxLogCount = 200
    private var logs: [T]
    private let queue = DispatchQueue(label: "\(String(describing: T.self))LoggerQueue",
                                      qos: .background,
                                      attributes: .concurrent)
    
    private let rxDisposeBag = DisposeBag()
    private var persistenceManager: PersistenceBridge<[T]>?
    
    // Specify constraints for `T`
    public init(maxCount: Int = 200, save: Bool = true, _ ext: String? = nil) {
        self.maxLogCount = maxCount
        if save {
            self.persistenceManager = PersistenceBridge<[T]>(storageKey: Self.storageKey(ext: ext))
            self.logs = self.persistenceManager?.load() ?? []
        }
        else {
            self.logs = []
        }
        
        NotificationCenter.default.rx.notification(UIApplication.willResignActiveNotification)
            .subscribe(onNext: {[weak self] notification in
                self?.persistenceSave()
            })
            .disposed(by: rxDisposeBag)
    }
    
    public var storageKey:String? { persistenceManager?.storageKey }
    
    private class func storageKey(ext: String? = nil) -> String {
        "ZM_\(ext ?? String(describing: T.self)).logs"
    }
    
    func persistenceSave() {
        persistenceManager?.save(logs)
    }
    
    public func addLog(_ log: T) {
        queue.async(flags: .barrier) {
            if self.logs.count >= self.maxLogCount {
                self.logs.removeLast()
            }
            self.logs.insert(log, at: 0)
        }
    }
    
    public func getLogs(where predicate: ((T) -> Bool)? = nil) -> [T] {
        queue.sync {
            predicate.map { logs.filter($0) } ?? logs
        }
    }
    
    public func removeLog(_ log: T) where T : Equatable {
        removeAll(where: {$0 == log})
    }
    
    public func clearLogs() {
        queue.async(flags: .barrier) {
            self.logs.removeAll()
        }
    }
    
    public func removeAll(where shouldBeRemoved: @escaping (T) -> Bool) {
        queue.async(flags: .barrier) {
            self.logs.removeAll(where: shouldBeRemoved)
        }
    }
    
    public func updateLogs(_ update:@escaping (inout [T]) -> Void) {
        queue.async(flags: .barrier) {
            update(&self.logs)
        }
    }
    
    public func updateLog(where predicate: @escaping (T) -> Bool, update: @escaping (inout T) -> Void) {
        queue.async(flags: .barrier) {
            if let index = self.logs.firstIndex(where: predicate) {
                update(&self.logs[index])
            }
        }
    }
    
    public func updateLog(where predicate: @escaping (T) -> Bool, log: T) {
        queue.async(flags: .barrier) {
            // 如果 log 有值，直接替换匹配的日志
            if let index = self.logs.firstIndex(where: predicate) {
                self.logs[index] = log
            }
            else {
                self.logs.append(log)
            }
        }
    }
}

public struct LogMsg:Codable,CustomStringConvertible, Settable {
    public var code,title,msg,properties: String?
    public var time: TimeInterval = Date().timeIntervalSince1970
    public var id: String? = UUID().uuidString
    
    public init(_ data: CustomStringConvertible? = nil) {
        msg = data?.description
    }
    
    public var description: String {
        toJson()?.merging(["time":time.date.timeStr()]).transitionString(separator: ", ") ?? "LogMsg error"
    }
    
    public mutating func marge(properties data: [String:Any]?) {
        properties = (properties?.toDictionary ?? [String:Any]()).merging(data).toJsonString()
    }
}

// 扩展 Setter
extension Setter: CustomStringConvertible where Subject == LogMsg {
    public var description: String { object.description }
}

extension CustomStringConvertible {
    public var logMsg: LogMsg { LogMsg(self) }
}

extension Error {
    public var logMsg: LogMsg { LogMsg(localizedDescription).setter.object }
}
